/**
 * &copy; 2006 NewInstance.it
 */
package it.newinstance.jrainbow.parser.xml;

import it.newinstance.jrainbow.parser.StateMachine;
import it.newinstance.jrainbow.source.TaggedChar;

/**
 * See XML Specification at http://www.w3.org/TR/REC-xml/
 * @author Luigi R. Viggiano
 * @version $Id: XMLStateMachine.java 152 2007-06-22 00:11:56Z luigi.viggiano $
 */
public enum XMLStateMachine implements StateMachine<XMLStateMachine> {

    CONTENT {
        @Override
        public XMLStateMachine handle(TaggedChar chr) {            
            if (chr.match("<?")) {
                chr.setTag(XMLTag.PROCESSING_INSTRUCTION_DELIMITER, 
                        - "<?".length());
                return PROCESSING_INSTRUCTION;                
            }

            if (chr.match("<") && 
                    isXMLNamePart(chr.getNext().getChar())) {
                chr.setTag(XMLTag.TAG_DELIMITER);
                return TAG;
            }
            
            if (chr.match("</")) {
                chr.setTag(XMLTag.TAG_DELIMITER, - "</".length());
                return TAG;                
            }

            if (chr.match("<!") && 
                    ! chr.getNext(2).match("<!--") &&
                    ! chr.getNext(7).match("<![CDATA[")) {
                chr.setTag(XMLTag.DECLARATION_DELIMITER, - "<!".length());
                return DECLARATION;
            }
            
            if (chr.match("<!--")) {
                chr.setTag(XMLTag.COMMENT_DELIMITER, - "<!--".length());
                return COMMENT;
            }
            
            if (chr.match("<![CDATA[")) {
                chr.setTag(XMLTag.CDATA_DELIMITER, - "<![CDATA[".length());
                return CDATA;
            }
            
            if ("&%".indexOf(chr.getChar()) != -1) {
                chr.setTag(XMLTag.ENTITY_DELIMITER);
                return ENTITY;
            }
            
            // other code
            chr.setTag(XMLTag.TEXT_CONTENT);
            return CONTENT;
        }
    },

    COMMENT {
        @Override
        public XMLStateMachine handle(TaggedChar chr) {
            if (chr.match("-->")) {
                chr.setTag(XMLTag.COMMENT_DELIMITER, - "-->".length());
                return CONTENT;
            }

            // other code
            chr.setTag(XMLTag.COMMENT_CONTENT);
            return COMMENT;
        }
    }, 
    
    PROCESSING_INSTRUCTION {
        private boolean firstIdentifier = true;
        @Override
        public XMLStateMachine handle(TaggedChar chr) {
            if (chr.match("?>")) {
                chr.setTag(XMLTag.PROCESSING_INSTRUCTION_DELIMITER,
                        - "?>".length());
                firstIdentifier = true;
                return CONTENT;                
            }
            
            if (isXMLNamePart(chr.getChar())) {
                if (firstIdentifier || chr.getPrevious()
                        .getTag() == XMLTag.PROCESSING_INSTRUCTION_NAME) {
                    chr.setTag(XMLTag.PROCESSING_INSTRUCTION_NAME);                    
                    firstIdentifier = false;
                    return PROCESSING_INSTRUCTION;
                } 
                chr.setTag(XMLTag.ATTRIBUTE_NAME);
                return PROCESSING_INSTRUCTION;
            } 
            
            if (chr.getChar() == '"') {
                chr.setTag(XMLTag.ATTRIBUTE_VALUE);
                DOUBLE_QUOTED_ATTRIBUTE.setReturnState(this);
                return DOUBLE_QUOTED_ATTRIBUTE;
            }

            if (chr.getChar() == '\'') {
                chr.setTag(XMLTag.ATTRIBUTE_VALUE);
                SINGLE_QUOTED_ATTRIBUTE.setReturnState(this);
                return SINGLE_QUOTED_ATTRIBUTE;
            }
            
            // other code
            chr.setTag(XMLTag.PROCESSING_INSTRUCTION_CONTENT);
            return PROCESSING_INSTRUCTION;
        }        
    }, 
    
    DOUBLE_QUOTED_ATTRIBUTE {
        @Override
        public XMLStateMachine handle(TaggedChar chr) {
            chr.setTag(XMLTag.ATTRIBUTE_VALUE);
            if (chr.getChar() == '"') 
                return getReturnState();
            return DOUBLE_QUOTED_ATTRIBUTE;
        }
        
    }
    , 
    
    SINGLE_QUOTED_ATTRIBUTE {        
        @Override
        public XMLStateMachine handle(TaggedChar chr) {
            chr.setTag(XMLTag.ATTRIBUTE_VALUE);
            if (chr.getChar() == '\'') 
                return getReturnState();
            return DOUBLE_QUOTED_ATTRIBUTE;
        }
    }, 
    
    DECLARATION {
        private boolean firstIdentifier = true;
        @Override
        public XMLStateMachine handle(TaggedChar chr) {

            
            if (chr.getChar() == '>') {
                chr.setTag(XMLTag.DECLARATION_DELIMITER);
                firstIdentifier = true;
                return CONTENT;
            }
            
            if (chr.match("PUBLIC")) {
                chr.setTag(XMLTag.DOCTYPE_PUBLIC_KEYWORD, - "PUBLIC".length());
                return DECLARATION;
            }            
            
            if (chr.match("SYSTEM")) {
                chr.setTag(XMLTag.DOCTYPE_SYSTEM_KEYWORD, - "SYSTEM".length());
                return DECLARATION;
            }            
            
            if (isXMLNamePart(chr.getChar())) {
                if (firstIdentifier || chr.getPrevious()
                        .getTag() == XMLTag.DECLARATION_NAME) {
                    chr.setTag(XMLTag.DECLARATION_NAME);                    
                    firstIdentifier = false;
                    return DECLARATION;
                } 
                chr.setTag(XMLTag.ATTRIBUTE_NAME);
                return DECLARATION;
            } 

            if (chr.getChar() == '"') {
                chr.setTag(XMLTag.ATTRIBUTE_VALUE);
                DOUBLE_QUOTED_ATTRIBUTE.setReturnState(this);
                return DOUBLE_QUOTED_ATTRIBUTE;
            }

            if (chr.getChar() == '\'') {
                chr.setTag(XMLTag.ATTRIBUTE_VALUE);
                SINGLE_QUOTED_ATTRIBUTE.setReturnState(this);
                return SINGLE_QUOTED_ATTRIBUTE;
            }
            
            chr.setTag(XMLTag.DECLARATION_CONTENT);
            return DECLARATION;
        }        
    }, 
    
    TAG {
        private boolean firstIdentifier = true;
        @Override
        public XMLStateMachine handle(TaggedChar chr) {
            if (chr.getChar() == '>') {
                if (chr.match("/>")) 
                    chr.setTag(XMLTag.TAG_DELIMITER, -"/>".length());
                else 
                    chr.setTag(XMLTag.TAG_DELIMITER);
                firstIdentifier = true;
                return CONTENT;
            }
            
            if (isXMLNamePart(chr.getChar())) {
                if (firstIdentifier || chr.getPrevious()
                        .getTag() == XMLTag.TAG_NAME) {
                    chr.setTag(XMLTag.TAG_NAME);                    
                    firstIdentifier = false;
                    return TAG;
                } 
                chr.setTag(XMLTag.ATTRIBUTE_NAME);
                return TAG;
            } 

            if (chr.getChar() == '"') {
                chr.setTag(XMLTag.ATTRIBUTE_VALUE);
                DOUBLE_QUOTED_ATTRIBUTE.setReturnState(this);
                return DOUBLE_QUOTED_ATTRIBUTE;
            }

            if (chr.getChar() == '\'') {
                chr.setTag(XMLTag.ATTRIBUTE_VALUE);
                SINGLE_QUOTED_ATTRIBUTE.setReturnState(this);
                return SINGLE_QUOTED_ATTRIBUTE;
            }
            
            chr.setTag(XMLTag.TAG_CONTENT);
            return TAG;
        }
    }, 
    
    CDATA {
        @Override
        public XMLStateMachine handle(TaggedChar chr) {
            if (chr.match("]]>")) {
                chr.setTag(XMLTag.CDATA_DELIMITER, - "]]>".length());
                return CONTENT;
            }
            chr.setTag(XMLTag.CDATA_CONTENT);
            return CDATA;
        }
    }, 
    
    ENTITY {
        @Override
        public XMLStateMachine handle(TaggedChar chr) {
            if (chr.getChar() == ';') {
                chr.setTag(XMLTag.ENTITY_DELIMITER);
                return CONTENT;
            }
            chr.setTag(XMLTag.ENTITY_NAME);
            return ENTITY;
        }
    }
    
    ;

    private XMLStateMachine returnState;

    protected void setReturnState(XMLStateMachine state) {
        returnState = state;
    }        
    
    protected XMLStateMachine getReturnState() {
        return returnState;
    }

    public abstract XMLStateMachine handle(TaggedChar chr);

    /*
     * see http://www.w3.org/TR/REC-xml/#NT-Name
     */
    private static boolean isXMLNamePart(char chr) {
        if (chr >= 'a' && chr <= 'z' || 
            chr >= 'A' && chr <= 'Z' ||
            chr >= '0' && chr <= '9' || 
            "-_:.".indexOf(chr) != -1)
            return true;
        return false;
    }
}
